package com.ly.mp.busicen.rule.flow;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
import org.apache.dubbo.config.utils.ReferenceConfigCache;
import org.apache.dubbo.rpc.service.GenericException;
import org.apache.dubbo.rpc.service.GenericService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import com.ly.mp.busicen.common.context.BusicenException;
import com.ly.mp.busicen.common.helper.SpringContextHolder;
import com.ly.mp.busicen.rule.flow.action.IAction;

public class FlowSpelUtil {
	
	private static Logger log = LoggerFactory.getLogger(FlowSpelUtil.class);
	
	public static StandardEvaluationContext buildSpelContext(Object context,Map<String, Method> methods) {
		StandardEvaluationContext econtext=new StandardEvaluationContext(context);
		if (methods==null||methods.isEmpty()) {
			methods = SEPEL_METHODS;			
		}
		methods.entrySet().stream().forEach(m->{
			econtext.registerFunction(m.getKey(), m.getValue());
		});
		return econtext;
	}
	
	public static void spelSetData(StandardEvaluationContext econtext,String el,Object data) {
		ExpressionParser parser = new SpelExpressionParser();
		Expression expression = parser.parseExpression(el);
		expression.setValue(econtext,data);
	}
	
	public static <T> T spelGetData(StandardEvaluationContext econtext,String el,Class<T> clazz) {
		ExpressionParser parser = new SpelExpressionParser();
		Expression expression = parser.parseExpression(el);
		return expression.getValue(econtext,clazz);
	}
	
	public static void spelSetData(IAction action,String el,Object data) {
		StandardEvaluationContext eContext = (StandardEvaluationContext) action.extention().get(IAction.EXTKEY_SPEL);
		spelSetData(eContext, el, data);
	}
	
	public static Object spelGetData(IAction action,String el) {
		return spelGetData(action, el, Object.class);
	}
	
	public static <T> T spelGetData(IAction action,String el,Class<T> clazz) {
		StandardEvaluationContext eContext = (StandardEvaluationContext) action.extention().get(IAction.EXTKEY_SPEL);
		return spelGetData(eContext, el,clazz);
	}
	
	public static <T> void exportService(Class<T> clazz, T t) {	
		try {
			ApplicationConfig application = (ApplicationConfig) SpringContextHolder.getBean(ApplicationConfig.class);
			if (application == null) {
				throw new RuntimeException("ApplicationConfig 未配置");
			}
			RegistryConfig registry = (RegistryConfig) SpringContextHolder.getBean(RegistryConfig.class);
			if (registry == null)
				throw new RuntimeException("RegistryConfig 未配置");

			ProtocolConfig protocol = (ProtocolConfig) SpringContextHolder.getBean(ProtocolConfig.class);

			try {
				ServiceConfig<T> service = new ServiceConfig<>();
				service.setApplication(application);
				service.setRegistry(registry);
				service.setProtocol(protocol);
				service.setInterface(clazz);
				service.setTimeout(120000);
				service.setFilter("busicenContext,-exception");
				service.setRef(t);
				service.export();
				log.info("export {} service success[{}]", clazz.getName(), service.getExportedUrls().get(0).getPort());
				
			} catch (Exception e) {
				log.error("export " + clazz.getName() + " service fail", e);
				throw new RuntimeException("export fail");
			}
		} catch (Exception e) {
			log.error("未配置dubbo");
		}
	}
	
	@SuppressWarnings("rawtypes")
	public static  ConcurrentHashMap<Class, Object> referenceCache= new ConcurrentHashMap<>();
	
	@SuppressWarnings("unchecked")
	public static <T> T importService(Class<T> clazz) {
		ApplicationConfig application = (ApplicationConfig) SpringContextHolder.getBean(ApplicationConfig.class);
		if (application == null) {
			throw new FlowException("ApplicationConfig 未配置");
		}
		RegistryConfig registry = (RegistryConfig) SpringContextHolder.getBean(RegistryConfig.class);
		if (registry == null)
			throw new FlowException("RegistryConfig 未配置");
		try {
			 Object dubboService = referenceCache.get(clazz);
			if (dubboService==null) {
				ReferenceConfig<T>reference = new ReferenceConfig<>();
				reference.setApplication(application);
				reference.setRegistry(registry);
				reference.setInterface(clazz);
				reference.setCluster("failsafe");
				reference.setFilter("busicenContext");
				reference.setTimeout(120000);
				reference.setCheck(false);
		        reference.setRetries(0);
		        ReferenceConfigCache cache = ReferenceConfigCache.getCache();
				dubboService =  cache.get(reference);
				referenceCache.put(clazz, dubboService);
			}			
			return (T) dubboService;
		} catch (GenericException e) {
			if(e.getExceptionClass().equals(BusicenException.class.getName())){
				throw BusicenException.create(e.getExceptionMessage());
			}
			throw e;
		}catch (Exception e) {
			log.error("动态获取dubbo实例失败:{}",e.getMessage());
			throw e;
		}
	}
	
	
	final static Map<String, Method> SEPEL_METHODS = new ConcurrentHashMap<>();	
	
	static {
		initMethods();			
	}
	
	static void initMethods() {
		Class<FlowSpelMethods> clazz = FlowSpelMethods.class;
		Method[] methods = clazz.getDeclaredMethods();
		Stream.of(methods).filter(m->Modifier.isStatic(m.getModifiers())).forEach(m->{
			SEPEL_METHODS.put(m.getName(), m);
		});		
	}
	
	public static  ConcurrentHashMap<String, GenericService> dynamicReferenceCache= new ConcurrentHashMap<>();
	
	public static Object dynamicInvoke(String intefaceType, String method, String[] paramTypes, Object[] args) {
		ApplicationConfig application = (ApplicationConfig) SpringContextHolder.getBean(ApplicationConfig.class);
		if (application == null) {
			throw new FlowException("ApplicationConfig 未配置");
		}
		RegistryConfig registry = (RegistryConfig) SpringContextHolder.getBean(RegistryConfig.class);
		if (registry == null)
			throw new FlowException("RegistryConfig 未配置");
		try {
			GenericService genericService = dynamicReferenceCache.get(intefaceType);
			if (genericService==null) {
				ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
				reference.setApplication(application);
				reference.setRegistry(registry);
				reference.setInterface(intefaceType);
				//reference.setGeneric(Boolean.valueOf(true));
				reference.setGeneric("true");				
				reference.setCluster("failsafe");
				reference.setFilter("busicenContext");
				reference.setTimeout(120000);
				reference.setCheck(false);
		        reference.setRetries(0);
				ReferenceConfigCache cache = ReferenceConfigCache.getCache();
				genericService = (GenericService) cache.get(reference);
				dynamicReferenceCache.put(intefaceType, genericService);
			}
			
			Object result = genericService.$invoke(method, paramTypes, args);
			return result;
		} catch (GenericException e) {
			if(e.getExceptionClass().equals(BusicenException.class.getName())){
				throw BusicenException.create(e.getExceptionMessage());
			}
			throw e;
		}catch (Exception e) {
			throw e;
		}
	}
}
